Pour exporter en slides, lancer : jupyter nbconvert Bases\ de\ données.ipynb --to slides --post serve


In [1]:
-- connection: dbname=cours user=cours

Bases de données

Master Humanités Numériques — université de Rouen

Bertrand Bordage de NoriPyt

  • Développeur, musicien & dirigeant
  • Membre de l’équipe de développement du CMS Wagtail
  • Créateur de Dezède avec l’université de Rouen
  • Créateur de nombreux projets open source

Introduction

Un peu de mise en contexte et explications avant de commencer.

Qu’entend-on par « base de données » ?

  • En sciences humaines, une application (web, de bureau ou mobile) permettant d’accéder facilement à des données stockées dans un SGBD.
    ⇒ Terme abusif, à éviter
  • Tout système informatique de stockage de données
    ⇒ « Système de Gestion de Bases de Données », soit SGBD ou DBMS en anglais
  • Un ensemble de données structuré par un SGBD

Dans le cadre de ce cours, on se concentrera sur les SGBD.

Exemples d’utilisations abusives du terme « base de données »

  • Dezède
  • SUDOC
  • Data BNF
  • Gallica

Pourquoi éviter ce terme abusif ?
Au même titre, Twitter, les services Google, et presque tout l’Internet moderne pourrait être nommé « base de données ».

Même un tableur type Excel ou une feuille de papier peuvent être appelés abusivement « base de données ».

Pas faux en soit, mais peu précis.

Redéfinissions « base de données »

une base de données ≠ un Système de Gestion de Base de Données

un SGBD = logiciel
une base de données = contenu

Dans l’ordre d’utilisation, on :

  • créé la structure de la base de données
  • ajoute des données à la base de données
  • effectue des requêtes sur ces données
  • ajoute des données
  • effectue des requêtes

Pourquoi Excel, Gallica et papier pas SGBD ?

Excel : non structuré, ne permet pas de faire de requêtes au-delà de filtrages basiques

Gallica : contient une base de données mais n’est pas un SGBD (pas de création de nouvelle base de données au sein de Gallica)

Papier : non structuré, ne permet pas de faire de requêtes.

En plus de devoir gérer les actions listées précédemment, un SGBD doit permettre de réaliser toutes ces tâches sous forme de code informatique, et être ainsi totalement automatisables. Cela permet d’intégrer les données dans n’importe quel programme : application web, de bureau, mobile.

Termes courants à connaître

Une base de données (ou database, DB, BDD) est composée de tables stockant des lignes suivant plusieurs colonnes.

Chaque table contient les données structurées suivant les mêmes colonnes.
Par exemple, la base de données « bibliothèque » contient les tables « livres », « auteurs », « genres », « collections », « éditeurs », etc.

Une ligne correspond à un ensemble de données représentant un seul objet.
Par exemple « Le Horla de Maupassant ».

Une colonne correspond à un champ. Par exemple « année de publication ».

Une valeur est la donnée élémentaire contenue dans une cellule, c’est-à-dire l’intersection d’une ligne et d’une colonne. Par exemple, l’année de publication du Horla de Maupassant est « 1886 ».

Ensuite, on lance une requête (sans lance-requêtes) pour demander à la base de données d’extraire des informations. Par exemple, on peut demander tous les livres de Maupassant, ou le nombre de livres fantastiques publiés entre 1880 et 1890.

Principaux types de bases de données

  • relationnelle
  • non relationnelle
  • SQL (« siquoueul »)
  • noSQL (« nosiquoueul »)

La plupart du temps :

  • bases de données relationnelles en SQL
  • bases de données non relationnelles en noSQL

Le type de base de données indispensable, vu dans ce cours :
les bases de données relationnelles en SQL

SQL, c’est quoi ?

Structured Query Language (= Langage de requêtes structurées)

Langage informatique créé en 1974 ayant abouti à une norme internationale

Un langage pour décrire la forme du résultat que l’on souhaite obtenir, et non le raisonnement.

SQL ≠ SGBD
SQL = langage
SGBD = logiciel pouvant être utilisé à l’aide d’un langage comme SQL

Célèbres SGBD où les requêtes s’écrivent en SQL :

Nom Prononciation idéale Prononciation à la française
MySQL maïèskyouèl maïèsküèl
SQLite sikoueulaïte èsküèlite
Oracle owreukeul oracle
Microsoft SQL server maïcrossoft èskyouèl seurveur microssoft esküèl serveur
PostgreSQL poste-graisse-kyouèl poste-graisse-küèl

Ce tableau est précieux, tout le monde se trompe sur ces prononciations.

Quel SGBD choisir ?

++ le plus répandu
+- gratuit mais dirigé par Oracle, et sous licence partiellement open source
-- structure laxiste aboutissant à des pertes de données

Quel SGBD choisir ?

++ le plus léger, il est partout (navigateurs, smartphones, …)
++ open source
-- sournois, car ne respecte aucune des structures pourtant obligatoires à déclarer

Quel SGBD choisir ?

++ le plus complet
-- coûte 17 500 € la licence (oui)

Quel SGBD choisir ?

++ coûte moins cher qu’Oracle
-- n’a aucun intérêt par rapport à PostgreSQL

Quel SGBD choisir ?

++ le plus complet et fiable des SGBD open source, quasiment aussi complet qu’Oracle
-- configuration des accès un peu complexe (mais pour une excellente sécurité)

Au menu du jour

PostgreSQL !

Parfait pour toutes les types de base de données jusqu’à des millards de données, ce qui suffira toute votre vie.

Nous n’aurons pas de problème de configuration des accès car nous passons par un service en ligne gérant ça pour nous.

Passons à la pratique !

Procédure de travail

Ouvrez un onglet de navigateur web sur NoriSQL : https://sql.noripyt.com

Trois parties :

  • en haut : commandes SQL structurant la base de données
  • au milieu : fichiers CSV pour insérer un fichier de données dans une table
  • en bas : commandes SQL pour effectuer des requêtes

Sous les deux champs permettant de remplir des requêtes s’affiche le résultat de ces requêtes, ou les erreurs contenues dans la requête.

À chaque modification puis lancement, l’URL de NoriSQL change : conservez chaque nouvelle URL pour chaque étape que vous souhaitez sauvegardez ! Créez un document que vous sauvegardez régulièrement contenant à chaque modification un descriptif de ce que vous venez de faire + l’URL.

Notre projet pour cet ensemble de cours

Créer une base de données de livres, ceux que vous adorez plus ou moins.

Au fur et à mesure de l’avancée du cours, on va rencontrer des problèmes nécessitant d’affiner la structure de la base de données.

Nous allons tous collaborer à un fichier Google Drive servant de source à cette base de données :
https://docs.google.com/spreadsheets/d/1xptGD4t671SJrdRum18GsJh65dILbDvHp4bzwD2W--Q/edit?usp=sharing

Utiliser ce fichier de source collaboratif permettra de vous faire voir les problématiques de formatage des données, le problème numéro un auquel vous ferez face.

Création d’une table

Création de la structure de la table :


In [2]:
CREATE TABLE books (id int, title text, author text,
                    pages int, is_author_alive boolean);

Types de données élémentaires :

Type SQL Exemple de donnée Traduction
boolean true ou false Booléen, soit vrai ou faux
int -723 Nombre entier
float 3.1415 Nombre à virgule avec précision incertaine (inadapté pour des prix)
text 'bonjour' Texte libre
varchar(57) 'salut' Texte libre, jusqu’à 57 caractères
date DATE '2017-10-16' Une date
time TIME '10:08:27' Une heure
timestamp TIMESTAMP '2017-10-16 10:08:27' Un moment dans l’histoire, donc une combinaison de date et heure

Ajout de données

Ajout sans préciser les colonnes :


In [3]:
INSERT INTO books
VALUES (1, 'Alice au pays des merveilles', 'Lewis Carroll', 196, false),
       (2, 'Les Piliers de la terre', 'Ken Follett', 1050, true);

Ajout en précisant les colonnes :


In [4]:
INSERT INTO books (id, title, author)
VALUES (3, 'De l’autre côté du miroir', 'Lewis Carroll'),
       (4, 'Un Monde sans fin', 'Ken Follett');

Exercice 1

Insérez au moins 3 de vos livres préférés dans la table books.

Ajouter ensuite ces livres dans le tableur Google Drive.

Exercice 2

À partir du tableur Google Drive, exporter en CSV pour importer toutes les données dans NoriSQL.

Sélection de données

Sélection de toutes les colonnes de toutes les lignes :


In [5]:
SELECT * FROM books;


idtitle author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
2Les Piliers de la terre Ken Follett 1050 1
3De l’autre côté du miroir Lewis Carroll
4Un Monde sans fin Ken Follett

Sélection de deux colonnes :


In [6]:
SELECT title, author FROM books;


title author
Alice au pays des merveillesLewis Carroll
Les Piliers de la terre Ken Follett
De l’autre côté du miroir Lewis Carroll
Un Monde sans fin Ken Follett

Exercice 3

Écrire une requête récupérant le titre et le nombre de pages de tous les livres

Alias de colonnes


In [7]:
SELECT title AS titre, author AS auteur FROM books;


titre auteur
Alice au pays des merveillesLewis Carroll
Les Piliers de la terre Ken Follett
De l’autre côté du miroir Lewis Carroll
Un Monde sans fin Ken Follett

Exercice 4

Écrire une requête récupérant le nom du livre et le nombre de pages, avec les noms de colonne nom et nombre_de_pages.

Limiter le nombre de données récupérées

La plupart des bases de données sont volumineuses, il est inutile d’aller chercher systématiquement la totalité des données.

Pour limiter le nombre de résultats :


In [8]:
SELECT * FROM books LIMIT 3;


idtitle author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
2Les Piliers de la terre Ken Follett 1050 1
3De l’autre côté du miroir Lewis Carroll

Pour sauter les premiers résultats :


In [9]:
SELECT * FROM books OFFSET 2;


idtitle author pages is_author_alive
3De l’autre côté du miroirLewis Carroll
4Un Monde sans fin Ken Follett

Exercice 5

Dans un site Internet, on affiche plusieurs pages de résultats.

Chaque page fait 4 résultats.

Écrire la requête listant les résultats de la page 2.

Filtrer les données

Sélection de tous les livres écrits par Lewis Carroll :


In [10]:
SELECT * FROM books WHERE author = 'Lewis Carroll';


idtitle author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
3De l’autre côté du miroir Lewis Carroll

Sélection de tous les livres écrits par Ken Follett de plus de 1100 pages :


In [11]:
SELECT * FROM books WHERE author = 'Ken Follett' AND pages < 1100;


idtitle author pages is_author_alive
2Les Piliers de la terreKen Follett 1050 1

Exercice 6

Écrire une requête récupérant les livres de votre auteur préféré.

Comparaisons

Exemple Traduction
a = b a est égal à b
a != b a est différent de b
a > b a est supérieur à b
a < b a est inférieur à b
a >= b a est supérieur ou égal à b
a <= b a est inférieur ou égal à b
a IS NULL a n’a pas été rempli (tout type sauf texte)
a IN (1, 2, 3) a est parmi les valeurs 1, 2 ou 3
a LIKE '%feuill__' a vaut 'feuillue' ou 'portefeuilles'

Combinaison de comparaisons :

Exemple Traduction
a AND b true si à la fois a et b sont true
a OR b true si soit a ou b est true, ou les deux
NOT a true si a est false

Exercice 7

Écrire une requête récupérant les livres dont le titre commence par « Le ».

Exercice 8

Écrire une requête récupérant les livres dont l’auteur est toujours vivant et a un nom contenant un « e ».

Exercice 9

Écrire une requête récupérant les livres dont l’un des deux champs suivants (ou les deux) n’est pas défini : pages ou is_author_alive

Mise à jour de données

Encore un ajout de données :


In [12]:
INSERT INTO books (title) VALUES ('La Chute des géants');

Oups, j’ai oublié quelques infos !


In [13]:
UPDATE books
SET id = 5, author = 'Ken Follett'
WHERE title = 'La Chute des géants';

Est-ce que ça a marché ?


In [14]:
SELECT * FROM books WHERE author = 'Ken Follett';


idtitle author pages is_author_alive
2Les Piliers de la terreKen Follett 1050 1
4Un Monde sans fin Ken Follett
5La Chute des géants Ken Follett

Oui !

Exercice 10

Remplir automatiquement le champ is_author_alive de tous les livres où il n’est pas défini. Remplir par défaut la valeur false.

Modification de la structure

Dans la vie professionnelle, on ne supprime pas les tables pour les recréer à chaque fois comme on fait aujourd’hui ensemble.

À la place, on fait migrer la base de données en modifiant sa structure.

Si on souhaite ajouter un champ :


In [15]:
ALTER TABLE books ADD COLUMN publication_year int;

In [16]:
ALTER TABLE books DROP COLUMN publication_year;

Mettre une valeur par défaut :


In [17]:
ALTER TABLE books ALTER COLUMN title SET DEFAULT '[Titre inconnu]';

Mais ce qui nous intéresse, c’est de rendre des champs obligatoires :


In [18]:
ALTER TABLE books ALTER COLUMN title SET NOT NULL,
                  ALTER COLUMN author SET NOT NULL;

Impossible désormais de ne pas remplir les champs titre et auteur !

Liste complète des possibilités de modification de structure : https://www.postgresql.org/docs/10/static/sql-altertable.html

Exercice 11

Rendre obligatoire l’identifiant et le titre.

Insérer une ligne sans l’identifiant, constater ce qu’il se passe.

Même chose pour le titre.

Exercice 12

Modifier la structure pour qu’on considère qu’un auteur soit décédé par défaut.


In [19]:
ALTER TABLE books ALTER COLUMN id SET NOT NULL;
ALTER TABLE books ALTER COLUMN is_author_alive SET DEFAULT false;

Suppression de données


In [ ]:
INSERT INTO books (title, author)
VALUES ('Alice in Wonderland', 'Lewis Carroll');

Oups, un doublon !

Retirons-le :


In [21]:
DELETE FROM books
WHERE title = 'Alice in Wonderland';

Et si le titre avait été le même ?

Exercice 13

Supprimer tous les livres d’un auteur que vous n’aimez pas.

(C’est le seul type d’autodafé qu’il est convenable de faire dans sa vie.)

Les identifiants automatiques, indispensables

Définir des identifiants soi-même, tâche rébarbative et source d’erreurs.

Les identifiants auto-incrémentés, ou numéros de série, sont là pour ça.

À chaque nouvel ajout dans la base de données, nouvel identifiant créé.

Très important ! L’identifiant d’une donnée supprimée n’est jamais réutilisé.

Pour créer un identifiant auto-incrémenté, on dispose du type serial.

Exercice 14

Supprimer la table.

Recréer la table en changeant l’identifiant de books en un identifiant auto-incrémenté.

Ne pas réimporter les données.

Insérer une ligne sans préciser id. Sélectionner toutes les lignes pour vérifier si id s’est rempli automatiquement.

Note : pour cette modification de structure importante et uniquement celle-là, il est difficile (mais possible) de le faire sans supprimer la table. C’est pourquoi on s’autorise ici à ne pas faire de commande ALTER TABLE.


In [22]:
DROP TABLE books;

CREATE TABLE books (id serial, title text NOT NULL, author text, pages int, is_author_alive boolean);

INSERT INTO books (title, author, pages, is_author_alive)
VALUES ('Alice au pays des merveilles', 'Lewis Carroll', 196, false),
       ('Les Piliers de la terre', 'Ken Follett', 1050, true),
       ('De l’autre côté du miroir', 'Lewis Carroll', NULL, NULL),
       ('Un Monde sans fin', 'Ken Follett', NULL, NULL),
       ('La Chute des géants', 'Ken Follett', NULL, NULL);

Un problème sauvage apparaît !

Problème : doublons possibles

Utiliser le type serial permet de ne plus avoir à saisir d’identifiants.

Un problème subsiste avec id, on peut utiliser deux fois le même identifiant :


In [23]:
INSERT INTO books (id, title, author)
VALUES (5, 'L’Hiver du monde', 'Ken Follett');

In [24]:
SELECT * FROM books WHERE id = 5;


idtitle author pages is_author_alive
5La Chute des géantsKen Follett
5L’Hiver du monde Ken Follett

In [25]:
DELETE FROM books WHERE id = 5 AND title = 'L’Hiver du monde';

Clé primaire, la solution

On peut marquer une colonne comme étant clé primaire, ou primary key. Ainsi, elle ne peut avoir deux fois la même valeur.

De plus, la clé primaire est la colonne officiellement utilisée comme identifiant par la base de données pour cette table.

On peut donc faire une clé primaire sur n’importe quelle colonne de n’importe quel type, tant que les données ne sont pas jamais en double. Par exemple :


In [26]:
CREATE TABLE countries (name varchar(100) PRIMARY KEY);
INSERT INTO countries VALUES ('France'), ('Belgique'), ('Italie');

Clé primaire, la solution (suite)

Rien de différent de d’habitude en sélectionnant les données :


In [27]:
SELECT * FROM countries;


name
France
Belgique
Italie

Toutefois, il est désormais impossible d’avoir un pays en doublon.

Dans le cas des livres, utiliser le titre comme identifiant est ambigu: si on vous parle de Confessions, pouvez-vous savoir de quel livre précis il s’agit ? Il y en a tellement eu, c’est impossible.

Mais dans le cas des pays, pas d’ambigüité: la liste est courte et stable, il ne devrait jamais y avoir de confusion sur le pays dont il s’agit quand on donne son nom. Utiliser le nom comme identifiant au lieu d’un nombre est ici adapté.

Exercice 15

Faire votre propre table contenant quelques pays.

Essayer d’insérer un doublon dans cette table pour constater ce qu’il se passe.

Exercice 16

Supprimer la table des pays.

Trouver d’autres scénarios que celui vu dans la slide précédente pour « forcer » la création d’un doublon dans la table books.

Pistes :

  • il n’y a pas que la commande INSERT INTO pour modifier les données de la table.
  • il n’y a pas forcément besoin de préciser une valeur à id pour créer un doublon.
  • un identifiant n’existant plus dans la table ne doit jamais être réutilisé.

Réécrire table de livres pour que la colonne id soit la clé primaire de la table.

Tenter à nouveau de créer des doublons de plusieurs manières différentes une fois la clé primaire créée.


In [28]:
DROP TABLE books;

CREATE TABLE books (id serial PRIMARY KEY, title text NOT NULL, author text, pages int, is_author_alive boolean);

INSERT INTO books (title, author, pages, is_author_alive)
VALUES ('Alice au pays des merveilles', 'Lewis Carroll', 196, false),
       ('Les Piliers de la terre', 'Ken Follett', 1050, true),
       ('De l’autre côté du miroir', 'Lewis Carroll', NULL, NULL),
       ('Un Monde sans fin', 'Ken Follett', NULL, NULL),
       ('La Chute des géants', 'Ken Follett', NULL, NULL);

SELECT * FROM books;


idtitle author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
2Les Piliers de la terre Ken Follett 1050 1
3De l’autre côté du miroir Lewis Carroll
4Un Monde sans fin Ken Follett
5La Chute des géants Ken Follett

Supprimer une table

Pour supprimer une table, il suffit d’utiliser cette syntaxe :


In [29]:
DROP TABLE countries;

Cette commande donne une erreur lorsque la table n’existe pas, ce qui est normal et souhaitable.

Toutefois, il existe un raccourci pour supprimer la table et ne pas déclencher d’erreur si elle n’existe pas :


In [30]:
DROP TABLE IF EXISTS countries;

Agréger les données

Sélection du nombre de livres :


In [31]:
SELECT count(*) FROM books;


count
5

Sélection du nombre total de pages dans la table :


In [32]:
SELECT sum(pages) FROM books;


sum
1246

Liste agrégations disponibles : https://www.postgresql.org/docs/10/static/functions-aggregate.html

Notamment min, max et avg.

Exercice 17

Écrire une requête récupérant le nombre de livres écrits par votre auteur préféré.

Exercice 18

Écrire une requête récupérant le nombre de livres contenant la syllabe tion dans le titre.

Exercice 19

Écrire une requête calculant le nombre moyen de pages par livre.

Exercice 20

Écrire une requête calculant le pourcentage de livres écrits par des auteurs vivants.

Indices :

  • on peut écrire un filtre dans une agrégation count à la place de *. Il n’y a pas à mettre de WHERE.
  • Pour obtenir un résultat de type float dans une division de nombre entiers, il faut transformer au moins un des deux nombres en float, en faisant CAST(my_number AS float).

In [33]:
SELECT count(*) FROM books WHERE author = 'Ken Follett';
SELECT count(*) FROM books WHERE title = '%tion%';
SELECT avg(pages) FROM books;

SELECT 100 * CAST(count(CASE WHEN is_author_alive THEN true
                        ELSE NULL END)
                  AS float)
       / count(*) || ' %' AS alive_ratio
FROM books;


alive_ratio
20 %

Ordonner les données

Ordonner les livres par ordre alphabétique croissant de titre :


In [34]:
SELECT * FROM books ORDER BY title;


idtitle author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
3De l’autre côté du miroir Lewis Carroll
5La Chute des géants Ken Follett
2Les Piliers de la terre Ken Follett 1050 1
4Un Monde sans fin Ken Follett

Ordonner les livres par ordre alphabétique croissant d’auteur, puis par ordre décroissant de titre :


In [35]:
SELECT * FROM books ORDER BY author ASC, title DESC;


idtitle author pages is_author_alive
4Un Monde sans fin Ken Follett
2Les Piliers de la terre Ken Follett 1050 1
5La Chute des géants Ken Follett
3De l’autre côté du miroir Lewis Carroll
1Alice au pays des merveillesLewis Carroll 196 0

Exercice 21

Écrire une requête affichant les quatre livres possédant le plus de pages.

Encore des soucis, désolé.

Non, les ordinateurs ne sont pas intelligents, et ne sont pas prêts de l’être.

Problème : ordres inattendus

Auteurs

« Ken Follett » devrait être après « Lewis Carroll », car F > C

Solution : séparer nom et prénom

Livres

Pour faciliter la recherche de titres, on ordonne généralement indépendamment du premier article.

« Les Piliers de la terre » devrait être après « Un Monde sans fin », car P > M

Solution : séparer l’article du titre

Exercice 22

Modifier la structure de la table pour corriger ces deux problèmes.

Il faut maintenant séparer manuellement les données.

Dans le fichier Google Drive, ajuster la structure de vos livres préférés conformément à la nouvelle structure.

Réimporter ensuite les données du Google Drive dans la base de données SQL.

Exercice 23

Écrire une requête sélectionnant à nouveau les données par ordre de titre pour constater qu’elles sont correctement ordonnées.

Réécrire la requête précédente pour que :

  • les deux parties du titre soient affichées en une seule colonne full_title
  • le prénom et le nom de l’auteur soient affichés en une seule colonne author

L’opérateur pour concaténer les chaînes de caractères est ||.


In [36]:
DROP TABLE books;

CREATE TABLE books (id serial PRIMARY KEY, title_prefix text, title text NOT NULL,
                    author_first_name text, author_last_name text, pages int, is_author_alive boolean);

INSERT INTO books (title_prefix, title, author_first_name, author_last_name, pages, is_author_alive)
VALUES ('', 'Alice au pays des merveilles', 'Lewis', 'Carroll', 196, false),
       ('Les ', 'Piliers de la terre', 'Ken', 'Follett', 1050, true),
       ('', 'De l’autre côté du miroir', 'Lewis', 'Carroll', NULL, NULL),
       ('Un ', 'Monde sans fin', 'Ken', 'Follett', NULL, NULL),
       ('La ', 'Chute des géants', 'Ken', 'Follett', NULL, NULL);

In [37]:
SELECT id, title_prefix || title AS full_title, author_first_name || ' ' || author_last_name AS author, pages, is_author_alive
FROM books
ORDER BY title;


idfull_title author pages is_author_alive
1Alice au pays des merveillesLewis Carroll 196 0
5La Chute des géants Ken Follett
3De l’autre côté du miroir Lewis Carroll
4Un Monde sans fin Ken Follett
2Les Piliers de la terre Ken Follett 1050 1

Problème : données déduites ou relatives

Dans notre table, un champ inadapté : is_author_alive

C’est une donnée qu’on peut déduire d’autres données : on déduit qu’un auteur est toujours vivant si nous n’avons pas trouvé sa date de décès.

La date de décès étant toujours plus précise que is_author_alive, on préfère stocker la date de décès, ce qui nous permettra de déduire si l’auteur est vivant.

De même, on pourrait souhaiter stocker l’âge de l’auteur.
Très mauvaise pratique ! donnée déduite des dates de naissance et décès, et relative au moment de la saisie.

En stockant dates de naissance et décès d’un auteur, on peut déduire beaucoup d’autres informations, comme :

  • Qui sont ses contemporains ?
  • Est-il mort plus vieux que la moyenne ?
  • De quel mouvement artistique peut-il se rapprocher ?

Exercice 24

Ajouter birth et death, les champs de date de naissance et décès.

Supprimer is_author_alive.

Dans le tableur Google Drive, ajouter ces données pour vos trois livres préférés.

Réimporter ces données dans la base de données SQL.

Exercice 25

Écrire une requête renvoyant uniquement les données suivantes :

  • prénom de l’auteur
  • nom de famille de l’auteur
  • est-ce que l’auteur est toujours vivant ?
  • âge de l’auteur en nombre d’années (au moment du décès, s’il est mort)

Attention : pour calculer l’âge de l’auteur, vous avez besoin de ces outils :

  • Pour calculer un âge, utiliser la fonction age(death, birth). Si l’auteur n’est pas encore mort, alors il faut écrire la date d’aujourd’hui à la place.
  • Pour obtenir la date d’aujourd’hui, faire now().
  • Pour utiliser la première valeur définie d’une liste de valeurs, utiliser la fonction COALESCE(a, b, c, …). Si a est définie (donc ne vaut pas NULL), la fonction renverra a. Si a vaut NULL et b est définie, alors cette fonction renverra b. Si seule c est définie, alors la fonction renverra c, etc.
  • Pour extraire le nombre d’années d’une date ou d’une différence de dates, utiliser EXTRACT(year from …), où on remplace les points de suspension par la données de laquelle extraire les années.

In [38]:
ALTER TABLE books ADD COLUMN author_birth date;
ALTER TABLE books ADD COLUMN author_death date;
ALTER TABLE books DROP COLUMN is_author_alive;
UPDATE books SET author_birth = '1949-6-5' WHERE author_last_name = 'Follett';
UPDATE books SET author_birth = '1832-1-27', author_death = '1898-1-14' WHERE author_last_name = 'Carroll';

In [39]:
SELECT author_first_name, author_last_name, author_death IS NULL AS is_author_alive,
       EXTRACT(year from age(COALESCE(author_death, now()), author_birth)) AS age
FROM books;


author_first_name author_last_name is_author_alive age
Ken Follett 1 68
Ken Follett 1 68
Ken Follett 1 68
Lewis Carroll 0 65
Lewis Carroll 0 65

Problème : la liste d’auteurs a des doublons

Première solution, lorsqu’on affiche les données :


In [40]:
SELECT DISTINCT author_first_name, author_last_name
FROM books
ORDER BY author_last_name;


author_first_name author_last_name
Lewis Carroll
Ken Follett

Toutefois, les données restent en double dans la base de données.

En cas de modification d’orthographe d’un nom d’auteur, ou de sa date de naissance, il faut modifier tous les livres de cet auteur !

La solution est de séparer les données des auteurs des données de livres, en gardant un lien entre les deux.

Exercice 26

Créer une nouvelle feuille dans le tableur pour contenir la table des auteurs.

Y recopier les données de vos auteurs préférés, telles que saisies dans la table des livres.

Attention, ne pas supprimer les données de la table des livres pour le moment

Exporter en TSV les deux feuilles, de sorte à créer la table authors et recréer une fois de plus la table books.

Ne pas oublier de corriger la structure de table générée par NoriSQL, c’est-à-dire faire attention aux champs obligatoires, clés primaires et types de colonnes.


In [41]:
CREATE TABLE authors (
    id serial PRIMARY KEY,
    first_name varchar(50),
    last_name varchar(50) NOT NULL,
    birth date NOT NULL,
    death date
);
INSERT INTO authors
VALUES (1, 'Lewis', 'Carroll', '1832-1-27', '1898-1-14'),
       (2, 'Ken', 'Follett', '1949-6-5', NULL);

SELECT * FROM authors;


idfirst_name last_name birth death
1Lewis Carroll 1832-01-271898-01-14
2Ken Follett 1949-06-05

Lier des tables

Maintenant que nous avons déplacé des données dans la table authors, il faut faire un lien avec la table books et supprimer les données d’auteurs de la table des livres.

Pour créer un lien entre deux tables, on stocke sur chaque ligne d’une une des deux tables la clé primaire d’une ligne de l’autre.

Exemple de table de livres :

id title author_id
1 Alice au Pays des merveilles 1
2 De l’autre côté du miroir 1
3 Les Piliers de la terre 2
4 La Chute des géants 2

Ainsi que la table d’auteurs correspondante :

id first_name last_name
1 Lewis Carroll
2 Ken Follett

Lier des tables (suite)

On écrit une règle permettant d’assurer que la ligne de la table liée existera toujours, pour éviter des liens morts. Cette règle spéciale est appelée une contrainte.

On a déjà défini des genres de contraintes : on a forcé le format des données et on a rendu des champs obligatoires, par exemple. Ce sont des genre de contraintes de saisie, mais cela ne s’appelle pas des contraintes dans le jargon des bases de données. On y reviendra.

Toutefois, on a déjà créé une vraie contrainte de base de données de manière déguisée : une clé primaire créé automatiquement une contrainte d’unicité, notée UNIQUE, afin qu’il soit impossible de créer des doublons d’identifiants.

Cette contrainte que l’on va écrire pour lier les tables est appelée une clé étrangère, ou foreign key, et est notée FOREIGN KEY.

Pour créer cette contrainte :


In [42]:
CREATE TABLE main_table (id serial PRIMARY KEY, related_id int);
CREATE TABLE related_table (id int PRIMARY KEY);

In [43]:
ALTER TABLE main_table
ADD CONSTRAINT main_table_fk FOREIGN KEY (related_id)
                             REFERENCES related_table (id);

In [44]:
DROP TABLE main_table;
DROP TABLE related_table;

Exercice 27

Dans le tableur, donner explicitement des identifiants uniques à chacun des auteurs que vous avez saisi.

Puis, saisir dans une colonne nommée author_id l’identifiant de chacun des auteurs que vous avez saisi.

Une fois que tout le monde a bien rempli la colonne author_id, une âme charitable se dévoue pour supprimer les colonnes des auteurs qu’on trouve dans la table des livres, hormis author_id bien sûr.

Exercice 28

Importer à nouveau les deux tables.

Ne pas oublier de corriger la structure de table générée par NoriSQL, c’est-à-dire faire attention aux champs obligatoires, clés primaires et types de colonnes.

Attention aussi, author_id ne doit pas être serial ni PRIMARY KEY, puisqu’il ne s’agit pas de l’identifiant de la table en cours, books, mais de la table étrangère liée, authors.

Créer la contrainte FOREIGN KEY entre les deux tables. Il est possible que la contrainte refuse de se créer s’il y a des erreurs dans les données. Corriger les données le cas échéant.

Sans faire appel au tableur+import, créer manuellement un livre d’un auteur déjà existant.

Puis, créer un livre d’un auteur n’existant pas encore.

Une fois cela fait, recopier les deux nouveaux livres ainsi tapés dans la base de données, de sorte que chacun ait créé 5 livres.


In [45]:
ALTER TABLE books ADD COLUMN author_id int;
UPDATE books SET author_id = 1 WHERE author_last_name = 'Carroll';
UPDATE books SET author_id = 2 WHERE author_last_name = 'Follett';
ALTER TABLE books DROP COLUMN author_first_name;
ALTER TABLE books DROP COLUMN author_last_name;
ALTER TABLE books DROP COLUMN author_birth;
ALTER TABLE books DROP COLUMN author_death;

ALTER TABLE books
ADD CONSTRAINT books_author_fk FOREIGN KEY (author_id)
                               REFERENCES authors (id);

SELECT * FROM books INNER JOIN authors ON (books.author_id = authors.id);


idtitle_prefix title pages author_id idfirst_name last_name birth death
1 Alice au pays des merveilles 196 1 1Lewis Carroll 1832-01-271898-01-14
3 De l’autre côté du miroir 1 1Lewis Carroll 1832-01-271898-01-14
2Les Piliers de la terre 1050 2 2Ken Follett 1949-06-05
4Un Monde sans fin 2 2Ken Follett 1949-06-05
5La Chute des géants 2 2Ken Follett 1949-06-05

Problème : réunir les données liées

Notre base de données est enfin mieux structurée ! Pour désigner la satisfaction qu’apporte une structure réussie, on dit que c’est une solution élégante.

Mais… Cela a beau être élégant, ce n’est pas pratique, on accède soit aux livres, soit aux auteurs, mais jamais aux deux en même temps :


In [46]:
SELECT * FROM books LIMIT 3;


idtitle_prefix title pages author_id
1 Alice au pays des merveilles 196 1
3 De l’autre côté du miroir 1
2Les Piliers de la terre 1050 2

In [47]:
SELECT * FROM authors;


idfirst_name last_name birth death
1Lewis Carroll 1832-01-271898-01-14
2Ken Follett 1949-06-05

Bien utile pour faire des efficacement des recherches sur les livres ou les auteurs, mais impossible de demander quels sont les noms des auteurs d’un ensemble de livres, ou les livres d’un auteur donné.

Les jointures, ou la puissance des relations

Jusqu’à présent, on a toujours sélectionné les données dans une seule table à l’aide de SELECT. Maintenant, on veut sélectionner dans deux tables à la fois. Pour cela, on fait une jointure, JOIN, en aujoutant aux données principales des données annexes. On précise également par quelle règle on fait le rapprochement entre les deux tables :


In [48]:
SELECT * FROM books
JOIN authors ON authors.id = books.author_id
LIMIT 3;


idtitle_prefix title pages author_id idfirst_name last_name birth death
1 Alice au pays des merveilles 196 1 1Lewis Carroll 1832-01-271898-01-14
3 De l’autre côté du miroir 1 1Lewis Carroll 1832-01-271898-01-14
2Les Piliers de la terre 1050 2 2Ken Follett 1949-06-05

ON fonctionne exactement comme WHERE, mais il se place uniquement après un JOIN. On peut ajouter un WHERE après un JOIN pour filtrer les données.

Ici, pour la première fois on précise le nom des tables avec la syntaxe table.colonne afin d’éviter les ambigüités. En effet, les deux tables ayant une colonne id, la base de données ne saurait quelle colonne choisir entre ces deux homonymes.

Exercice 29

Réécrire la requête joignant les deux tables, mais en n’affichant pas les deux colonnes (ici peu pertinentes) montrant l’identifiant de l’auteur.

Retirer également la limite.

Exercice 30

Écrire une requête listant uniquement le titre de tous les livres écrits par l’auteur dont le nom de famille est Follett.

Exercice 31

Écrire une requête listant les cinq livres écrits par les auteurs nés le plus récemment.

Problème : les traductions

Un même livre peut avoir plusieurs titres en fonction de sa langue de traduction. D’autres données varient aussi avec la traduction : l’année de publication, le nombre de pages, l’éditeur, et même les auteurs ! En effet, on peut compter le traducteur dans les auteurs, mais laissons ce problème de côté car nous ne gérons qu’un auteur par livre.

Il existe de nombreuses méthodes pour gérer des traductions. On pourrait ajouter une nouvelle colonne original_title, mais cette donnée serait alors redondante entre les différentes traductions d’un même livre. On pourrait aussi séparer la colonne title en title_en, title_fr, title_de etc. Dans certains cas, ces techniques sont pertinentes, mais ici ce n’est pas le cas, car on peut avoir un livre dans des centaines de langues, or on ne va pas s’amuser à avoir des centaines de colonnes juste pour le titre. De plus, il reste le problème des autres données variant avec la langue, notamment le nombre de pages.

Voici la solution nous convenant : on va considérer qu’une traduction est un livre à part de l’original. Toutefois, on va conserver un lien entre la traduction et l’original, en précisant la langue des livres.

Exercice 32

Ajouter deux colonnes translation_of et language pour gérer les traductions. translation_of doit être une foreign key d’un livre vers un autre livre. language contiendra le code de langue de 2 lettres suivant la norme ISO 639-1.

Exercice 33

Ajouter au document Google Drive trois livres en langue étrangère que vous aimez, à la fois en version originale et en traduction française.

Si certains de vos livres déjà saisis sont des livres en langue étrangère, bien s’assurer que l’original et la traduction soient saisis.

Exercice 34

Écrire une requête allant chercher tous les livres en anglais.

Exercice 35

Écrire une requête allant chercher tous les livres pour lesquels il existe une traduction dans la base de données.

Exercice 36

Écrire une requête déduisant la langue maternelle de chaque auteur. En effet, on peut considérer qu’un livre qui n’a pas de traduction est dans la langue maternelle de l’auteur.

Exercice 37

Écrire une requête allant chercher les titres complets originaux & traduits et les noms d’auteurs complets de tous les livres pour lesquels il existe une traduction dans la base de données.


In [49]:
ALTER TABLE books ADD COLUMN translation_of int REFERENCES books(id);
ALTER TABLE books ADD COLUMN language varchar(2);
UPDATE books SET language = 'fr';

INSERT INTO books (title_prefix, title, author_id, language)
VALUES ('', 'Alice in Wonderland', 1, 'en'),
       ('', 'Through the Looking-Glass', 1, 'en'),
       ('The ', 'Pillars of the Earth', 2, 'en'),
       ('', 'World Without End', 2, 'en'),
       ('', 'Fall of Giants', 2, 'en');
       
UPDATE books SET translation_of = 6 WHERE id = 1;
UPDATE books SET translation_of = 7 WHERE id = 3;
UPDATE books SET translation_of = 8 WHERE id = 2;
UPDATE books SET translation_of = 9 WHERE id = 4;
UPDATE books SET translation_of = 10 WHERE id = 5;

In [50]:
SELECT
    books.title_prefix || books.title AS original_title,
    translated.title_prefix || translated.title AS translated_title,
    authors.first_name || ' ' || authors.last_name AS author
FROM books
JOIN books AS translated
ON translated.translation_of = books.id
JOIN authors ON authors.id = books.author_id;


original_title translated_title author
Alice in Wonderland Alice au pays des merveillesLewis Carroll
Through the Looking-GlassDe l’autre côté du miroir Lewis Carroll
The Pillars of the Earth Les Piliers de la terre Ken Follett
World Without End Un Monde sans fin Ken Follett
Fall of Giants La Chute des géants Ken Follett

Statistiques complexes

On a vu comment faire des statistiques simples en agrégant les données. Une agrégation simple permet d’avoir une donnée : une moyenne, une somme, etc.

Les statistiques complexes nécessitent d’avoir une succession d’agrégations de données.

Exemples de statistiques complexes : graphe, camembert, diagramme de Venn, diagramme de cordes.

Pour obtenir une succession d’agrégations de données, on doit grouper par une autre colonne ces agrégations. Par exemple, « le nombre de livres par langue » se traduit par « on souhaite compter le nombre de livres pour une langue donnée ». On doit donc faire une agrégation count. Et plus précisément, on doit « grouper par langue les livres, puis utiliser l’agrégation count pour connaître le nombre par langue ». On écrit donc :


In [51]:
SELECT language, count(books)
FROM books
GROUP BY language;


language count
fr 5
en 5

Exercice 38

Écrire une requête renvoyant la moyenne de nombre de pages par langue.

Exercice 39

De la requête précédente, on serait tenter de déduire quel langue est plus courte à écrire qu’une autre. Cependant, des livres non traduits peuvent exister dans la base de données, et perturber notre statistique. En effet, qu’un livre fasse 800 pages en anglais ne nous apprend rien sur la densité de l’anglais. Mais apprendre qu’un livre de 800 pages en anglais se traduit par 950 pages en français est plus parlant.

Écrire une requête renvoyant le pourcentage de caractères en plus en français par rapport à l’anglais, en n’utilisant que les livres pour lesquels on a les versions anglaises et françaises.

On exclut évidemment de la requête tous les livres pour lesquels une des deux versions (française ou anglaise) n’a pas de nombre de pages.


In [52]:
SELECT avg(books.pages) / avg(other.pages) FROM books
JOIN books AS other
ON (other.translation_of = books.id OR other.id = books.translation_of) AND other.language = 'fr'
WHERE books.language = 'en';


?column?

Statistiques complexes (suite)

Attention : pour des raisons logiques, toutes les colonnes dans SELECT autres que des agrégations doivent forcément être dans GROUP BY. Ici, language est bien dans les deux. On pourrait avoir envie d’afficher le titre des livres en même temps qu’on affiche le nombre. Mais ça n’aurait pas de sens, car l’objectif du GROUP BY est de rassembler plusieurs lignes suivant une même donnée. On ne pourrait pas mettre sur une même ligne « fr » tous les titres de livres français.

Statistiques complexes (suite)

Lorsqu’on fait des statistiques complexes, on souhaite presque toujours ordonner les données par une ou plusieurs colonnes ayant servi à regrouper ces données. Par exemple :


In [53]:
SELECT language, count(books)
FROM books
GROUP BY language
ORDER BY language;


language count
en 5
fr 5

Statistiques complexes (suite)

Tout l’intérêt des bases de données relationnelles est de pouvoir faire des statistiques en se servant des relations entre les données.

Comme précédemment, on groupe par une ou plusieurs colonnes tout en effectuant une agrégation. Toutefois, on y ajoute une jointure pour rassembler les tables liées.

Par exemple, pour connaître le nombre de livres pour chaque auteur, on fait :


In [54]:
SELECT first_name, last_name, count(books)
FROM authors
JOIN books ON books.author_id = authors.id
GROUP BY first_name, last_name
ORDER BY last_name;


first_name last_name count
Lewis Carroll 4
Ken Follett 6

Exercice 40

Faire une requête récupérant le nombre moyen de pages par auteur.

Exercice 41

Déduire un âge moyen par langue maternelle d’auteur.


In [55]:
SELECT
    language,
    EXTRACT(year FROM avg(age(coalesce(death, now()), birth))) AS average_age
FROM authors
JOIN books ON books.author_id = authors.id
WHERE translation_of IS NULL
GROUP BY language;


language average_age
en 67

Exemple de représentation de données

On peut afficher de nombreuses manières les statistiques extraites.

Généralement, on génère une image à l’aide d’un autre outil, en partant des statistiques renvoyées par la base de données. Toutefois, une base de données est si puissante qu’on peut écrire la logique pour afficher un graphique directement en SQL.

Voici comment réaliser un diagramme circulaire à partir de presque n’importe quelle statistique : https://sql.noripyt.com/58725bf9/1

C’est très moche et en pratique on ne fait jamais cela, mais cela montre bien que les données sorties par notre requête GROUP BY sont prêtes pour une représentation graphique. De nos jours, on préférera générer un fichier SVG à l’aide d’outils externes.

Table intermédiaire many-to-many

Pour l’instant, notre base de données est incapable de gérer plusieurs auteurs pour un même livre. C’est handicapant, car on omet systématiquement le traducteur. Bien entendu, c’est plus gênant encore pour les publications scientifiques et autres ouvrages à auteurs multiples.

Un mauvais réflexe consiste à ajouter une seconde colonne, author2_id, puis une troisième author3_id, etc. Cette technique a l’avantage d’être simple à construire, mais elle souffre de deux importantes failles :

  • Obligation d’avoir un nombre maximum d’auteurs. En général on commence à mettre jusqu’à 5 colonnes d’auteurs, mais dès qu’un ouvrage arrive avec plus d’auteurs, il faut faire appel au technicien pour ajouter de nouvelles colonnes, ou faire passer à la trappe quelques auteurs.
  • Pour des requêtes complexes comme des jointures, tout se complique très vite, car il faudrait joindre plusieurs fois la table authors.

Table intermédiaire many-to-many (suite)

La solution propre consiste à créer une table intermédiaire entre books et authors. On l’appelera books_authors

Chaque ligne de books_authors lie un livre à un auteur. On peut mettre plusieurs lignes mentionnant le même livre, et plusieurs lignes mentionnant le même auteur. Par exemple :

book_id author_id
1 1
1 3
2 1
2 4

Ici, Alice au pays des merveilles (1) est lié à deux auteurs, Lewis Carroll (1) et Henri Bué (3). De l’autre côté du miroir (2) est lié aussi à deux auteurs, Lewis Carroll (1) et Paul Gilson (4).

On peut aussi noter que Lewis Carroll (1) est donc lié à deux livres.

Comme plusieurs auteurs peuvent être liés à un seul livre, et que plusieurs livres peuvent être liés à un seul auteur, on appelle cette relation un many-to-many.

Exercice 42

Dans le tableur Google Drive, créer une table many-to-many nommée book_authors liant les auteurs aux livres. Y inscrire le lien entre livres et auteurs qui était stocké précedemment dans la colonne author_id des livres.

Supprimer la colonne author_id des livres du tableur Google Drive.

Créer dans le tableur Google Drive tous les traducteurs des livres dont on a déjà les traductions. Ajouter le lien entre les livres et ces traducteurs.

Réimporter les données dans NoriSQL avec cette nouvelle structure. Ne pas oublier de faire les changements de type de colonnes comme d’habitude.

Exercice 43

Écrire une requête récupérant les livres et leurs auteurs (traducteurs inclus).


In [56]:
CREATE TABLE books_authors (book_id int REFERENCES books(id),
                            author_id int REFERENCES authors(id));
INSERT INTO authors VALUES (3, 'Jean', 'Rosenthal', '1923-1-1', NULL);
INSERT INTO books_authors VALUES (6, 1), (7, 1), (8, 2), (9, 2), (10, 2),
                                 (1, 1), (3, 1), (2, 2), (4, 2), (5, 2), (2, 3);
ALTER TABLE books DROP COLUMN author_id;

In [57]:
SELECT books.*, authors.*
FROM books
JOIN books_authors ON books_authors.book_id = books.id
JOIN authors ON authors.id = books_authors.author_id
ORDER BY title, last_name;


idtitle_prefix title pages translation_oflanguage idfirst_name last_name birth death
1 Alice au pays des merveilles 196 6fr 1Lewis Carroll 1832-01-271898-01-14
6 Alice in Wonderland en 1Lewis Carroll 1832-01-271898-01-14
5La Chute des géants 10fr 2Ken Follett 1949-06-05
3 De l’autre côté du miroir 7fr 1Lewis Carroll 1832-01-271898-01-14
10 Fall of Giants en 2Ken Follett 1949-06-05
4Un Monde sans fin 9fr 2Ken Follett 1949-06-05
2Les Piliers de la terre 1050 8fr 2Ken Follett 1949-06-05
2Les Piliers de la terre 1050 8fr 3Jean Rosenthal 1923-01-01
8The Pillars of the Earth en 2Ken Follett 1949-06-05
7 Through the Looking-Glass en 1Lewis Carroll 1832-01-271898-01-14
9 World Without End en 2Ken Follett 1949-06-05

Regrouper plusieurs auteurs dans la même cellule

La requête de l’exercice 43 renvoie plusieurs fois les mêmes résultats. En effet, s’il y a 3 auteurs pour un même livre, la jointure fera que le livre sera renvoyé une fois pour chaque auteur, donnant quelque chose du genre :

Livre Auteur
Alice au pays des merveilles Lewis Carroll
Alice au pays des merveilles Henri Bué

Lorsque les résultats sont affichés sur un site Internet, les auteurs sont généralement regroupés par le langage de programmation du site (Python, PHP, Java, etc), mais restent ainsi en SQL.

Regrouper plusieurs auteurs dans la même cellule (suite)

Mais grâce à PostgreSQL, on peut rassembler ces données joliment à l’aide de l’agrégation STRING_AGG. Il faut y adjoindre un GROUP BY, car on ne souhaite pas agréger les données de la table en une seule donnée, mais bien en les agréger en une donnée par livre :


In [58]:
SELECT books.title_prefix || books.title AS book,
       STRING_AGG(authors.first_name || ' ' || authors.last_name,
                  ', ' ORDER BY last_name, first_name) AS authors
FROM books
JOIN books_authors ON books_authors.book_id = books.id
JOIN authors ON authors.id = books_authors.author_id
GROUP BY books.id
ORDER BY title;


book authors
Alice au pays des merveillesLewis Carroll
Alice in Wonderland Lewis Carroll
La Chute des géants Ken Follett
De l’autre côté du miroir Lewis Carroll
Fall of Giants Ken Follett
Un Monde sans fin Ken Follett
Les Piliers de la terre Ken Follett, Jean Rosenthal
The Pillars of the Earth Ken Follett
Through the Looking-Glass Lewis Carroll
World Without End Ken Follett

Affinons sans fin

Bonne nouvelle ! On a vu la quasi totalité des problématiques de base de données et leurs solutions en SQL.

Maintenant il s’agit d’affiner toujours plus notre modélisation de données pour qu’elle gère tous les cas dont on a besoin en se servant toujours des mêmes outils.

Exercice 44

Maintenant qu’on peut stocker plusieurs auteurs par livre, le problème est qu’ils sont à égalité : on ne peut pas différencier l’auteur du traducteur ! Ajouter dans la table books_authors un champ profession stockant ce qu’a fait la personne : 'author', 'translator', 'proofreader', etc.

Adapter la requête précédente pour qu’à côté de chaque nom d’auteur soit sa tâche entre parenthèses. Par exemple : Ken Follett (author), Jean Rosenthal (translator).

Exercice 45

Encore un souci, les auteurs restent ordonnés par nom de famille, or on préférerait avoir l’auteur d’abord. Ajouter un champ de nombre entier position permettant de mettre 1 pour l’auteur principal, 2 pour l’auteur secondaire, etc.

Adapter la requête de l’exercice 44 pour que les auteurs soient bien ordonnés par ce nombre position.

Exercice 46

Évidemment, ces deux nouvelles données, profession et position, deviennent très redondantes. Il serait mieux de les regrouper dans une table, car la position devra toujours la même pour une même task. De plus, cela permettra d’éviter les fautes d’orthographe.

Créer une table professions contenant les deux champs. Appeler name l’ancien champ profession. Bien penser à créer les clés étrangères.

Réécrire la requête de l’exercice 45 en utilisant cette nouvelle table de professions.

Exercice 47

Faire une amélioration sensée de la base de données. Celle que vous voulez !

Il y a énormément de possibilités, quelques idées :

  • gérer l’année de publication de chaque livre, et ainsi permettre de faire un classement chronologique de sortie des livres pour un auteur donnée
  • gérer les différentes éditions d’un même livre
  • stocker un lien vers la version numérisée sur Gallica
  • stocker l’ISBN
  • stocker le lieu de conservation du manuscrit autographe, lorsqu’il existe (en deux champs, bibliothèque + cote)
  • stocker le lieu de naissance et de décès de chaque auteur
  • stocker le contenu de chaque livre en plein texte pour pouvoir rechercher à l’intérieur des livres

In [59]:
DROP TABLE IF EXISTS books_authors;
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;

Merci !

Twitter : @NoriPytCom - GitHub : @BertrandBordage